home *** CD-ROM | disk | FTP | other *** search
/ Amiga Tools 4 / Amiga Tools 4.iso / tools / netzwerk / tcp-ip / slategames / backgammon.rexx next >
OS/2 REXX Batch file  |  1996-02-26  |  32KB  |  1,130 lines

  1. /* Backgammon for AmiSlate v1.0! */
  2. /* This program should be run on a screen with at least 8 colors */
  3.  
  4. /* Get our host's name--always given as first argument when run from Amislate */
  5. parse arg CommandPort ActiveString
  6.  
  7. if (length(CommandPort) == 0) then do
  8.     say ""
  9.     say "Usage:  rx backgammon.rexx <REXXPORTNAME>"
  10.     say "        (REXXPORTNAME is usually AMISLATE)"
  11.     say ""
  12.     say "Or run from the Rexx menu within AmiSlate."
  13.     say ""
  14.     exit 0
  15.     end
  16.     
  17. /* Send all commands to this host */
  18. address (CommandPort) 
  19. options results
  20.  
  21. lock ON
  22.  
  23. /* See if we're connected */
  24. GetRemoteStateAttrs stem rstateattrs.
  25. if (rstateattrs.mode > -1) then do
  26.         /* Parse command line argument to see if we've been activated by 
  27.            a remote request or a local user */
  28.         check = upper(left(ActiveString,3))
  29.         if (upper(left(ActiveString,3)) ~= 'RE') then 
  30.             GlobData.localplayer = 1
  31.         else
  32.             GlobData.localplayer = -1
  33.         end
  34.     else do
  35.         GlobData.localplayer = 0    /* i.e. we're both players */
  36.         end
  37.         
  38. if (GlobData.localplayer > 0) then do
  39.     call SetStatus("Requesting game from remote user... please wait.")
  40.     RemoteRexxCommand '"'||"Would you like to play backgammon?"||'"' "slaterexx:backgammon.rexx"
  41.     
  42.         waitevent stem handshake. MESSAGE
  43.         if (handshake.message == 0) then 
  44.         do
  45.             call SetStatus("Backgammon Game Refused")
  46.             lock off
  47.             exit 0
  48.         end
  49.     end
  50.  
  51. call SetStatus("Beginning Backgammon...")
  52.  
  53. call ResetGameState
  54. call SetGlobalData
  55. if (GlobData.localplayer >= 0) then call DrawBoard
  56.  
  57. call NextTurn
  58. call HandleEvents
  59.  
  60. lock OFF
  61. exit 0
  62.  
  63. /* Global Data structure:
  64.  
  65.     GlobData.Xspace.[step] (int)     : horizontal pixel offsets to the center of each coord (0-16)
  66.     GlobData.XSize    (int)         : total width of each element
  67.     GlobData.Yspace.[step] (int)     : vertical pixel offsets to the center of each coord (0-16)
  68.     GlobData.YSize    (int)         : total height of each element
  69.  
  70.     (board state)
  71.         GlobData.slots.[slotnum]         : array of slotstates (0..23) (int, > 0 = Pl1 pieces, < 0 = Pl-1 pieces)
  72.                      : note: slot -1 is player 1's "out" pile, and slot 24 is player -1's "out" pile.
  73.     GlobData.die.[dienum]            : the current number on each of the four die.  Usually only 0 & 1 are used,            
  74.  
  75.     (game state)
  76.     GlobData.turn             : whose turn it is
  77.     GlobData.exited.[playernum]      : number of pieces that have been taken off the board for player [playernum]
  78.     GlobData.localplayer              : which player is on local machine; 0 if both are
  79.      (color info)
  80.     GlobData.PieceColor.[playernum]  : color of pieces for this player
  81.     GlobData.SpikeColor.[playernum]  : spike color, alternates
  82.  
  83. */
  84.  
  85. /* --------------------------------------------------------------- */
  86. /* procedure HandleEvents                        */
  87. /* --------------------------------------------------------------- */
  88. HandleEvents: procedure expose GlobData. AMessage.
  89.  
  90. SetSquare = -10
  91. BReversed = 0
  92. BNeedsToRoll = 1
  93. BMustForfeit = 0
  94.  
  95. AMessage.TIMEOUT     = 1    /* No events occurred in specified time period */
  96. AMessage.MESSAGE     = 2    /* Message recieved from remote Amiga */
  97. AMessage.MOUSEDOWN   = 4    /* Left mouse button press in drawing area */
  98. AMessage.MOUSEUP     = 8    /* Left mouse button release in drawing area */
  99. AMessage.RESIZE      = 16    /* Window was resized--time to redraw screen? */ 
  100. AMessage.QUIT        = 32    /* AmiSlate is shutting down */
  101. AMessage.CONNECT     = 64    /* Connection established */
  102. AMessage.DISCONNECT  = 128    /* Connection broken */
  103. AMessage.TOOLSELECT  = 256    /* Tool Selected */
  104. AMessage.COLORSELECT = 512    /* Palette Color selected */
  105. AMessage.KEYPRESS    = 1024    /* Key pressed */
  106. AMessage.MOUSEMOVE   = 2048     /* Mouse was moved */
  107.  
  108. /* Turn on help */
  109. BHelp = 1
  110.  
  111. do while(1)
  112.     waitevent stem event. RESIZE MOUSEDOWN MOUSEUP TOOLSELECT MESSAGE DISCONNECT
  113.  
  114.     if (event.type == AMessage.QUIT) then exit 0
  115.     
  116.     if (event.type == AMessage.DISCONNECT) then do
  117.         call SetStatus("Connection broken--both players now local.")
  118.         GlobData.localplayer = 0
  119.         end
  120.    
  121.     if (event.type == AMessage.MESSAGE) then do
  122.         /* parse the message */
  123.         if (left(event.message,1) = "G") then do
  124.             /* It's our move! */
  125.         BNeedsToRoll = 1
  126.         BMustForfeit = 0
  127.         call NextTurn
  128.         end
  129.     else do
  130.         parse var event.message rfrom rto
  131.         call MovePiece((rfrom+0), (rto+0), 0)  /* update our internals--the (+0) forces the vars back into numeric format */
  132.         end
  133.     end
  134.  
  135.     if (event.type == AMessage.RESIZE) then do
  136.         if ((GlobData.localplayer == GlobData.turn)|(GlobData.localplayer == 0)) then do
  137.             call SetGlobalData
  138.             call DrawBoard
  139.             if (BMustForfeit == 1) then call ShowCantMove
  140.         end
  141.         else do
  142.             /* else just update our internal vars */
  143.             call SetGlobalData
  144.         end
  145.         /* call UpdateStatus */
  146.     end
  147.     
  148.     if ((event.type == AMessage.MOUSEDOWN)&((GlobData.turn = GlobData.localplayer)|(GlobData.localplayer == 0))) then do
  149.         whatclicked = ParseMouseClick(event.x, event.y)
  150.     if (SetSquare == -10) then do
  151.         if (whatclicked == 25) then 
  152.             if (BNeedsToRoll == 1) then do
  153.                 call Roll
  154.                 BNeedsToRoll = 0
  155.                 /* Check to see if we can move */
  156.                 can = CanMove()
  157.                 if (can == 0) then do
  158.                     call ShowCantMove
  159.                     BMustForfeit = 1
  160.                     end
  161.                 end
  162.             else do
  163.                 if (BMustForfeit == 1) then do
  164.                     /* Cancels rest of turn! */
  165.                     BNeedsToRoll = 1
  166.                     BMustForfeit = 0
  167.                     if (localplayer ~= 0) then sendmessage G
  168.                     call NextTurn
  169.                     end
  170.                 end
  171.         else 
  172.         /* Nothing was clicked before, mark this square? */
  173.         if ((BMustForfeit = 0)&(BNeedsToRoll = 0)&(whatclicked < 25)&((GlobData.slots.whatclicked * GlobData.turn) > 0)) then do
  174.             if (PieceCanMove(whatclicked) == 1) then do
  175.                 call HiliteTopPiece(whatclicked)
  176.                 if (BHelp == 1) then call ShowMoves(whatclicked)
  177.                 BReversed = ~BReversed
  178.                 SetSquare = whatclicked
  179.                 end
  180.                 else DisplayBeep LOCAL
  181.             end
  182.         end
  183.     else do
  184.         /* Already had one clicked, now either cancel it or move? */
  185.         if (whatclicked == SetSquare) then do
  186.             /* cancel the piece-move! */
  187.             if (BReversed == 1) then call HiliteTopPiece(whatclicked)
  188.             if (BHelp == 1) then call ClearArrows
  189.             BReversed = 0
  190.             SetSquare = -10
  191.             end
  192.         else do
  193.             dietouse = MoveOkay(SetSquare, whatclicked,1) 
  194.             if (dietouse > -1) then do
  195.                 if (BHelp == 1) then call ClearArrows
  196.                 call MovePiece(SetSquare, whatclicked, 1)
  197.                 BReversed = 0
  198.                 SetSquare = -10
  199.                 GlobData.die.dietouse = 0
  200.                 call DrawDie(dietouse)
  201.                 if ((GlobData.die.0 == 0)&(GlobData.die.1 == 0)&(GlobData.die.2 == 0)&(GlobData.die.3 == 0)) then do
  202.                     BNeedsToRoll = 1
  203.                     BMustForfeit = 0
  204.                     if (localplayer ~= 0) then sendmessage G
  205.                     call NextTurn
  206.                     end
  207.                 else do
  208.                     /* Check to see if we can still move */
  209.                     can = CanMove()
  210.                     if (can == 0) then do
  211.                         call ShowCantMove
  212.                         BMustForfeit = 1
  213.                         end
  214.                     end
  215.                 end
  216.                 else DisplayBeep LOCAL
  217.             end
  218.         end
  219.     end
  220.     end
  221.     return 1
  222.  
  223.  
  224. /* --------------------------------------------------------------- */
  225. /* procedure NextTurn                           */
  226. /*                                    */
  227. /* --------------------------------------------------------------- */
  228. NextTurn: procedure expose GlobData.
  229.     GlobData.Turn = -GlobData.Turn
  230.     do i = 0 to 3 
  231.         GlobData.die.i = 0
  232.         end
  233.  
  234.     /* local game code */
  235.     if (Globdata.localplayer == 0) then do
  236.         call DrawCup(0)
  237.         if (GlobData.turn == 1) then 
  238.             call SetStatus("Player 1, it's your turn to roll.")
  239.         else
  240.             call SetStatus("Player 2, it's your turn to roll.")
  241.         return 1
  242.         end
  243.     
  244.     /* two machine game code */
  245.     if (GlobData.turn == GlobData.localplayer) then do
  246.         call DrawCup(0)
  247.         if (GlobData.turn == 1) then 
  248.             call SetStatus("Player 1, it's your turn to roll.")
  249.         else
  250.             call SetStatus("Player 2, it's your turn to roll.")
  251.         end
  252.     else do
  253.         call SetStatus("Wait for other player to move.")
  254.         end
  255.     return 1
  256.     
  257.         
  258.  
  259. /* --------------------------------------------------------------- */
  260. /* procedure Roll                           */
  261. /*                                    */
  262. /* --------------------------------------------------------------- */
  263. Roll: procedure expose GlobData.
  264.  
  265.     /* First clear the die area */
  266.     cturn = GlobData.turn
  267.     SetFPen GlobData.PieceColor.cturn
  268.     square GlobData.XSpace.7+1 GlobData.YSpace.6+1 GlobData.XSpace.9-1 GlobData.YSpace.10-1 FILL
  269.  
  270.     GlobData.die.0 = random(1,6,time('s'))
  271.     GlobData.die.1 = random(1,6,time('s')+500)
  272.     
  273.     call DrawDie(0)
  274.     call DrawDie(1)
  275.     
  276.     if (GlobData.die.0 == GlobData.die.1) then do
  277.         call SetStatus("You rolled a double!")
  278.  
  279.         GlobData.die.2 = GlobData.die.1
  280.         GlobData.die.3 = GlobData.die.1
  281.     
  282.         call DrawDie(2)
  283.         call DrawDie(3)    
  284.         end
  285.     else do
  286.         if (GlobData.turn == 1) then 
  287.             call SetStatus("Player 1, move your pieces")
  288.         else
  289.             call SetStatus("Player 2, move your pieces")
  290.         end
  291.     return 1        
  292.  
  293.     
  294.  
  295. /* --------------------------------------------------------------- */
  296. /* procedure MovePiece                           */
  297. /*                                    */
  298. /* --------------------------------------------------------------- */
  299. MovePiece: procedure expose GlobData.
  300.     parse arg from, to, showgfx
  301.         
  302.     origfrom = GlobData.slots.from
  303.     negone = -1
  304.     
  305.     /* If it's a two machine game, tell the other machine what we're doing */
  306.     if ((showgfx == 1)&(GlobData.localplayer ~= 0)) then call TransmitMove(from, to)
  307.     
  308.     /* First remove the piece from the old spike and redraw it */
  309.     call RemovePiece(from, GlobData.turn, showgfx)
  310.  
  311.     /* If the piece is moving "off the board", count it up */
  312.     if (to == 24) then do
  313.         /* Piece exited the board for player 1 */
  314.         GlobData.exited.1 = (GlobData.exited.1) + 1
  315.         call CheckForWin
  316.         return 1
  317.         end
  318.     else if (to == -1) then do
  319.         /* Piece exited the board for player 1 */
  320.         GlobData.exited.negone = (GlobData.exited.negone) + 1
  321.         call CheckForWin
  322.         return 1
  323.         end        
  324.  
  325.     /* Now add/replace our piece to the to spike */
  326.     if ((origfrom * GlobData.slots.to) < 0) then do
  327.         /* a piece got killed! */
  328.         call SetStatus("Bump!  Ahahahahaha!!!!")
  329.         if (GlobData.localplayer ~= 0) then DisplayBeep REMOTE
  330.         if (GlobData.slots.to < 0) then 
  331.             call AddPiece(24, -1, showgfx)
  332.         else 
  333.             call AddPiece(-1, 1, showgfx)
  334.             
  335.         GlobData.slots.to = 0
  336.         end
  337.  
  338.     call AddPiece(to, GlobData.turn, showgfx)
  339.     return 1
  340.  
  341.  
  342.  
  343. /* --------------------------------------------------------------- */
  344. /* procedure RemovePiece                       */
  345. /* --------------------------------------------------------------- */
  346. RemovePiece: procedure expose GlobData.    
  347.     parse arg from, which, showgfx
  348.     
  349.     GlobData.slots.from = GlobData.slots.from - which    
  350.     if (showgfx == 1) then call DrawSpike(from)
  351.     return 1
  352.     
  353.     
  354. /* --------------------------------------------------------------- */
  355. /* procedure AddPiece                           */
  356. /* --------------------------------------------------------------- */
  357. AddPiece: procedure expose GlobData.
  358.     parse arg to, which, showgfx
  359.     
  360.     GlobData.slots.to = GlobData.slots.to + which
  361.     if (showgfx == 1) then call DrawPiece(to, GlobData.PieceColor.which, abs(GlobData.slots.to), 0)
  362.     return 1
  363.     
  364.  
  365. /* --------------------------------------------------------------- */
  366. /* procedure MoveOkay                           */
  367. /*                                    */
  368. /* returns the index of the die to use if the move is acceptable,  */
  369. /* or -1 if it is illegal.                        */
  370. /*                                    */
  371. /* --------------------------------------------------------------- */
  372. MoveOkay: procedure expose GlobData.
  373.     parse arg from, to, CountExits
  374.  
  375.     negone = -1
  376.     numsquares = abs(to-from)
  377.         
  378.     /* Side specific rules! */
  379.     if (GlobData.slots.from < 0) then do
  380.         /* no moving backwards */
  381.         if (to > from) then return -1
  382.  
  383.         /* no moving anyone else if we have a guy "out" */
  384.         if ((GlobData.slots.24 ~= 0)&(from ~= 24)) then return -1
  385.  
  386.         /* No moves into the "out" unless all of our pieces are (exited or in the last quad) */
  387.         /* Also, we can't exit on a roll larger than necessary *unless* there is no other use for that roll. */
  388.         if (to <= -1) then do
  389.             disttoexit = from + 1
  390.  
  391.             /* If we're not checking for exits, return failure now */
  392.             if (CountExits == 0) then do
  393.                 /* Still need to watch for an exact out! */
  394.                 do checkdie = 0 to 3 
  395.                     if (GlobData.die.checkdie == disttoexit) then 
  396.                         if (CanMoveOut(1) == 1) then return checkdie
  397.                     end                
  398.                 return -1
  399.                 end                
  400.             
  401.             /* Otherwise check to see if we can move out */
  402.             if (CanMoveOut(-1) == 0) then return -1
  403.             else do
  404.                 do checkdie = 0 to 3 
  405.                     if (GlobData.die.checkdie == disttoexit) then return checkdie
  406.                     end
  407.                 do checkdie = 0 to 3
  408.                     if (GlobData.die.checkdie > disttoexit) then do
  409.                         if (CanExitOnExtra(GlobData.die.checkdie) == 1) then return checkdie
  410.                         end
  411.                     end
  412.                 /* default: can't move out */
  413.                 return -1
  414.                 end
  415.             end
  416.         end
  417.     else do
  418.         /* no moving backwards */
  419.         if (to < from) then return -1
  420.  
  421.         /* no moving anyone else if we have a guy "out" */
  422.         if ((GlobData.slots.negone ~= 0)&(from ~= -1)) then return -1
  423.  
  424.         /* No moves into the "out" unless all of our pieces are (exited or in the last quad) */
  425.         if (to >= 24) then do
  426.             disttoexit = 24 - from
  427.  
  428.             /* If we're not checking for exits, return failure now */
  429.             if (CountExits == 0) then do
  430.                 /* Still need to watch for an exact out! */
  431.                 do checkdie = 0 to 3 
  432.                     if (GlobData.die.checkdie == disttoexit) then 
  433.                         if (CanMoveOut(1) == 1) then return checkdie
  434.                     end                
  435.                 return -1
  436.                 end
  437.                 
  438.             /* Otherwise check to see if we can move out */
  439.             if (CanMoveOut(1) == 0) then 
  440.                 return -1 
  441.             else do
  442.                 do checkdie = 0 to 3
  443.                     if (GlobData.die.checkdie == disttoexit) then return checkdie
  444.                     end
  445.                 do checkdie = 0 to 3
  446.                     if (GlobData.die.checkdie > disttoexit) then do
  447.                         if (CanExitOnExtra(GlobData.die.checkdie) == 1) then return checkdie
  448.                         end
  449.                     end
  450.                 /* default: can't move out */
  451.                 return -1
  452.                 end
  453.             end
  454.         end
  455.     
  456.     /* first get this into absolute numbers */
  457.     if (GlobData.slots.from < 0) then do
  458.         fromsq = -(GlobData.slots.from)
  459.         tosq   = -(GlobData.slots.to)
  460.         end
  461.     else do
  462.         fromsq = GlobData.slots.from
  463.         tosq   = GlobData.slots.to
  464.         end
  465.     
  466.     /* At this point: fromsq > 0 */
  467.     if (tosq < -1) then return -1
  468.     
  469.     /* Make sure this move is available on one of the die */
  470.     diefound = -1
  471.     do i = 0 to 3
  472.         if (GlobData.die.i == numsquares) then diefound = i
  473.         end
  474.     
  475.     return diefound
  476.     
  477.     
  478.     
  479.  
  480. /* --------------------------------------------------------------- */
  481. /* procedure HiliteTopPiece                       */
  482. /* --------------------------------------------------------------- */
  483. HiliteTopPiece: procedure expose GlobData.
  484.     parse arg spikenum
  485.  
  486.     cturn = GlobData.turn
  487.     
  488.     call DrawPiece(spikenum, GlobData.PieceColor.cturn, abs(GlobData.slots.spikenum), 1)
  489.     return 1
  490.  
  491.  
  492.  
  493. /* --------------------------------------------------------------- */
  494. /* procedure ParseMouseClick                        */
  495. /*                                   */
  496. /* returns (-1)-(24) if a stack was clicked, or 25 if the center   */
  497. /* dice/cup area was clicked                        */
  498. /*                                   */
  499. /* --------------------------------------------------------------- */
  500. ParseMouseClick: procedure expose GlobData.
  501.     parse arg xp, yp
  502.     
  503.     /* figure out vertical zone */
  504.     yZone = 1    /* default */
  505.     if (yp < GlobData.YSpace.6) then yZone = 0
  506.     if (yp > GlobData.YSpace.10) then yZone = 2
  507.     
  508.     /* figure out horizontal zone */
  509.     xZone = 1    /* default */
  510.     if (xp < GlobData.XSpace.7) then xZone = 0
  511.     if (xp > GlobData.XSpace.9) then xZone = 2
  512.     
  513.     /* are we in the dice/cup zone? */
  514.     if (xZone == 1) then do
  515.        if (yZone == 0) then return -1
  516.        if (yZone == 1) then return 25
  517.        return 24
  518.        end
  519.     else do
  520.        /* force into yzone 0 or 2 */
  521.        if (yp < GlobData.YSpace.8) then 
  522.            yzone = 0 
  523.        else 
  524.            yzone = 2
  525.        end
  526.        
  527.     /* Must be a regular stack--get the horizontal offset */    
  528.     if (xZone == 0) then offset = (xp-(GlobData.XSize/2)) / GlobData.XSize         
  529.     if (xZone == 2) then offset = ((xp-GlobData.XSpace.9-(GlobData.XSize/2)) / GlobData.XSize)
  530.     
  531.     offset = trunc(offset)    
  532.     if (offset < 0) then offset = 0
  533.     if (offset > 5) then offset = 5
  534.  
  535.     if ((xZone = 0)&(yZone = 0)) then return 11-offset
  536.     if ((xZone = 2)&(yZone = 0)) then return 5-offset
  537.     if ((xZone = 0)&(yZone = 2)) then return 12+offset
  538.     if ((xZone = 2)&(yZone = 2)) then return 18+offset
  539.     return -55
  540.  
  541.  
  542. /* --------------------------------------------------------------- */
  543. /* procedure ResetGameState                       */
  544. /* --------------------------------------------------------------- */
  545. ResetGameState: procedure expose GlobData.
  546.     negone = -1;
  547.     
  548.     DO i = -1 to 24
  549.         GlobData.slots.i = 0    /* first clear the board */
  550.     end
  551.  
  552.     /* initial board config */
  553.     GlobData.slots.0 = 2
  554.     GlobData.slots.5 = -5
  555.     GlobData.slots.7 = -3
  556.     GlobData.slots.11 = 5
  557.     GlobData.slots.12 = -5
  558.     GlobData.slots.16 = 3
  559.     GlobData.slots.18 = 5
  560.     GlobData.slots.23 = -2
  561.  
  562.  
  563. /* Setup to test end game -- dont use
  564. GlobData.slots.6 = -1
  565. GlobData.slots.5 = -2
  566. GlobData.slots.4 = -2
  567. GlobData.slots.3 = -5
  568. GlobData.slots.2 = -1
  569. GlobData.slots.1 = -3
  570. GlobData.slots.0 = -1
  571.  
  572. GlobData.slots.15 = 1
  573. GlobData.slots.18 = 2
  574. GlobData.slots.19 = 2
  575. GlobData.slots.20 = 5
  576. GlobData.slots.21 = 1
  577. GlobData.slots.22 = 3
  578. GlobData.slots.23 = 1
  579. */
  580.  
  581.     GlobData.slots.negone = 0
  582.     GlobData.slots.24 = 0
  583.  
  584.     GlobData.out.1 = 0
  585.     GlobData.out.negone = 0
  586.  
  587.     GlobData.die.0 = 0
  588.     GlobData.die.1 = 0
  589.     GlobData.die.2 = 0
  590.     GlobData.die.3 = 0
  591.  
  592.     GlobData.exited.1 = 0
  593.     GlobData.exited.negone = 0
  594.     
  595.     GlobData.turn    = -1;
  596.  
  597.     return 1
  598.  
  599. /* --------------------------------------------------------------- */
  600. /* procedure SetGlobalData                       */
  601. /* --------------------------------------------------------------- */
  602. SetGlobalData: procedure expose GlobData.
  603.     negone = -1
  604.     
  605.     /* Check to see whether we are connected */
  606.        GetWindowAttrs stem winattrs.
  607.        BoardWidth = winattrs.width  - 58
  608.        BoardHeight= winattrs.height - 55
  609.  
  610.     /* Set up offsets */
  611.     fStep = 1/16
  612.     DO i=0 to 16 
  613.       GlobData.Xspace.i = trunc(BoardWidth * (fStep * i))
  614.       GlobData.Yspace.i = trunc(BoardHeight * (fStep * i))
  615.       end
  616.  
  617.     GlobData.XSize = trunc(BoardWidth / 16)
  618.     GlobData.YSize = trunc(BoardHeight / 16)
  619.  
  620.        if (winattrs.depth < 3) then do
  621.         EasyRequest BackGammon_Error '"'||"You need at least a 8-color screen to play backgammon!"||'"' '"'||"Abort Backgammon"||'"'
  622.         call SetStatus("Backgammon game exited.")
  623.         lock off
  624.         exit 0
  625.         end 
  626.  
  627.     GlobData.SpikeColor.1 = 2
  628.     GlobData.SpikeColor.negone = 7
  629.     GlobData.PieceColor.1 = 4
  630.     GlobData.PieceColor.negone = 3
  631.     return 1
  632.  
  633.  
  634. /* --------------------------------------------------------------- */
  635. /* procedure DrawBoard                                             */
  636. /* --------------------------------------------------------------- */
  637. DrawBoard: procedure expose GlobData.
  638.     SetFColor 0 0 0        /* Get a black pen */
  639.  
  640.     Clear
  641.  
  642.     SetWindowTitle '"' || "Hang on, drawing the board..." || '"'
  643.  
  644.     SetFPen 1
  645.     square GlobData.Xspace.0 GlobData.Yspace.0 GlobData.XSpace.16 GlobData.YSpace.16
  646.     Do i = -1 to 24
  647.         call DrawSpike(i)
  648.         end
  649.  
  650.     SetFPen 1
  651.     line GlobData.Xspace.7 GlobData.Yspace.0 GlobData.XSpace.7 GlobData.YSpace.16
  652.     line GlobData.Xspace.9 GlobData.Yspace.0 GlobData.XSpace.9 GlobData.YSpace.16
  653.     square GlobData.XSpace.7 GlobData.YSpace.6 GlobData.XSpace.9 GlobData.YSpace.10    
  654.         
  655.     if ((GlobData.die.0 + GlobData.die.1 + GlobData.die.2 + GlobData.die.3) > 0)  then do
  656.         cturn = GlobData.turn
  657.         bgcol = GlobData.PieceColor.cturn
  658.         SetFPen bgcol
  659.         square GlobData.XSpace.7+1 GlobData.YSpace.6+1 GlobData.XSpace.9-1 GlobData.YSpace.10-1 FILL
  660.         Do i = 0 to 3 
  661.             call DrawDie(i)
  662.             end
  663.         end
  664.     else call DrawCup(0)
  665.  
  666.     call SetStatus("_LAST")
  667.     return 1
  668.  
  669.  
  670. /* --------------------------------------------------------------- */
  671. /* procedure DrawSpike                                             */
  672. /* --------------------------------------------------------------- */
  673. DrawSpike: procedure expose GlobData.
  674.     parse arg spikenum
  675.  
  676.     negone = -1
  677.  
  678.     /* Figure out what color to draw the spike as */
  679.     spikecol = GlobData.SpikeColor.1
  680.     if ((spikenum // 2) == 1) then spikecol = GlobData.SpikeColor.negone
  681.  
  682.     /* Figure out what color to draw any pieces as */
  683.     piececol = GlobData.PieceColor.1
  684.     numpieces  = GlobData.slots.spikenum
  685.     if (numpieces < 0) then do
  686.         piececol = GlobData.PieceColor.negone
  687.         numpieces = -numpieces
  688.         end
  689.  
  690.     /* Figure out our coords */
  691.     baseofspike = getSpikeBase(spikenum)
  692.     topofspike = getSpikeTop(spikenum)
  693.     spikecenter = getSpikecenter(spikenum)
  694.  
  695.     /* First blank out the area we are to draw on */
  696.     wid = trunc(GlobData.XSize/3)
  697.     BaseOfSpikeCoord = GlobData.YSpace.baseofspike + getSpikeDir(spikenum)
  698.     TopOfSpikeCoord = GlobData.Yspace.topofspike 
  699.     if ((spikenum == -1)|(spikenum == 24)) then TopOfSpikeCoord = TopOfSpikeCoord - getSpikeDir(spikenum)
  700.     /* extra is the few more pixels on the left we add in to ensure that any extra layers are erased */
  701.     extra = trunc((trunc((GlobData.slots.spikenum)/5)+1)*(GlobData.XSize/10))
  702.     SetFPen 0    
  703.     square (GlobData.Xspace.spikecenter)-wid-extra BaseOfSpikeCoord (GlobData.Xspace.spikecenter)+wid TopOfSpikeCoord FILL
  704.  
  705.     SetFPen 1
  706.     if ((spikenum >= 0) & (spikenum <= 23)) then do
  707.       /* Draw the spike */
  708.       line (GlobData.Xspace.spikecenter)-wid BaseOfSpikeCoord GlobData.Xspace.spikecenter TopOfSpikeCoord
  709.       line (GlobData.Xspace.spikecenter)+wid BaseOfSpikeCoord GlobData.Xspace.spikecenter TopOfSpikeCoord
  710.     
  711.       /* calculate circle coords */
  712.       cx = trunc(GlobData.XSize / 4)
  713.       cy = trunc(GlobData.YSize / 2)
  714.  
  715.       SetFPen spikecol    
  716.       flood GlobData.Xspace.spikecenter trunc((BaseOfSpikeCoord + TopOfSpikeCoord)/2)
  717.       circle GlobData.Xspace.spikecenter TopOfSpikeCoord cx cy FILL
  718.       SetFPen 1
  719.       circle GlobData.Xspace.spikecenter TopOfSpikeCoord cx cy 
  720.       end
  721.  
  722.     /* Draw pieces if any */
  723.     if (numpieces > 0) then do 
  724.         do i=1 to numpieces 
  725.             call DrawPiece(spikenum, piececol, i, 0) 
  726.             end
  727.         end
  728.     return 1    
  729.  
  730.  
  731. /* Draws a piece on the spikenum spike, in pen piecepen, at position piecenum (where 0 is the 
  732.    base of the spike, and 5 is the top */
  733. DrawPiece: procedure expose GlobData.
  734.     parse arg spikenum, piecepen, piecenum, BXor
  735.  
  736.     dx = 0
  737.     dy = 0
  738.     
  739.     /* Additional rows if piecenum is > 5 */
  740.     do while (piecenum > 5)
  741.         piecenum = piecenum - 5
  742.         dx = dx - GlobData.XSize/10
  743.         dy = dy - GlobData.YSize/10
  744.         end
  745.  
  746.     dx = trunc(dx)
  747.     dy = trunc(dy)
  748.     
  749.     cx = getSpikeCenter(spikenum)
  750.     cy = getSpikeBase(spikenum) + (getSpikeDir(spikenum) * piecenum)
  751.  
  752.     rx = trunc(GlobData.XSize / 3)
  753.     ry = trunc(GlobData.YSize / 2)
  754.  
  755.  
  756.     if (BXor == 0) then do
  757.         SetFPen piecepen
  758.         circle (GlobData.Xspace.cx + dx) (GlobData.Yspace.cy + dy) rx ry FILL
  759.         SetFPen 1
  760.         circle (GlobData.Xspace.cx + dx) (GlobData.Yspace.cy + dy) rx ry 
  761.         end
  762.     else circle (GlobData.Xspace.cx + dx) (GlobData.Yspace.cy + dy) rx ry FILL XOR
  763.     
  764.     return 1
  765.  
  766.  
  767. /* Draws a cup indicating that a player may roll.  The cup is drawn with the color
  768.    indicated by (cupcolor) */
  769. DrawCup: procedure expose GlobData.
  770.     parse arg BXor 
  771.     
  772.     negone = -1
  773.  
  774.     left = trunc((GlobData.XSpace.7 + GlobData.XSpace.8)/2)
  775.     right = trunc((GlobData.XSpace.8 + GlobData.XSpace.9)/2)
  776.  
  777.     top = trunc((GlobData.YSpace.6 + GlobData.YSpace.7)/2)
  778.     bottom = trunc((GlobData.YSpace.9 + GlobData.YSpace.10)/2)
  779.     
  780.     if (BXor == 1) then do
  781.         square left top right bottom XOR FILL
  782.         return 1
  783.         end
  784.     
  785.     /* Clear the whole area */
  786.     SetFPen 0
  787.     square GlobData.XSpace.7+1 GlobData.YSpace.6+1 GlobData.XSpace.9-1 GlobData.YSpace.10-1 FILL
  788.  
  789.     ctr = trunc((right+left)/2)
  790.     midht  = trunc(GlobData.YSize/2)
  791.  
  792.     SetFPen 1
  793.     circle ctr top+midht trunc((right-left)/2) midht FILL
  794.     line left top+midht left bottom-midht
  795.     line right top+midht right bottom-midht
  796.     circle ctr bottom-midht trunc((right-left)/2) midht
  797.             
  798.     /* Fill it in */
  799.     currentturn = GlobData.turn
  800.     setFPen GlobData.PieceColor.currentturn
  801.     square left+1 top+midht+midht+2 right-1 bottom-midht FILL    
  802.     flood ctr bottom-midht+1
  803.     flood ctr top+midht+midht+1 
  804.     return 1
  805.  
  806.  
  807.  
  808. /* Draws dice number (dieindex) to show the number indicated in GlobData.die.(dieindex) */
  809. DrawDie: procedure expose GlobData.
  810.     parse arg dieindex
  811.  
  812.     left = 7
  813.     top = 7
  814.     if (dieindex >= 2) then top = top + 1
  815.     if (dieindex == 1) then left = left + 1
  816.     if (dieindex == 3) then left = left + 1
  817.  
  818.     /* convert to "real" coordinates */
  819.     left = GlobData.Xspace.left
  820.     top  = GlobData.Yspace.top
  821.  
  822.     /* shrink it 25% */
  823.     width = trunc(GlobData.XSize * 0.75)
  824.     height = trunc(GlobData.YSize * 0.75)
  825.  
  826.     /* and recenter it by moving it down&right 12.5% */
  827.     left = left + trunc(GlobData.XSize * 0.125)
  828.     top  = top  + trunc(GlobData.YSize * 0.125)
  829.  
  830.     /* now calculate the right and bottom edges */
  831.     right = left + width
  832.     bottom = top + height
  833.  
  834.     /* number to show on the die */
  835.     num = GlobData.die.dieindex
  836.  
  837.     /* if it's unset, just erase the area */
  838.     if (num < 1) then do
  839.         /* Calculate background color = player's piece color */
  840.         cturn = GlobData.turn
  841.         SetFPen GlobData.PieceColor.cturn
  842.         square left top right bottom FILL
  843.         return 1
  844.         end
  845.  
  846.     /* start with blank die */
  847.     SetFColor 15 15 15
  848.     square left top right bottom FILL
  849.     SetFPen 1
  850.     square left top right bottom 
  851.  
  852.     dotrx = trunc(width/12)        /* radii of dot */
  853.     dotry = trunc(height/12)
  854.     dx = trunc(width / 4)        /* offset from center dot */
  855.     dy = trunc(height / 4)
  856.  
  857.     x2 = trunc((right + left)/2)
  858.     y2 = trunc((bottom + top)/2)
  859.  
  860.     x1 = x2 - dx
  861.     x3 = x2 + dx
  862.     y1 = y2 - dy
  863.     y3 = y2 + dy
  864.  
  865.     if (num == 1) then do
  866.         circle x2 y2 dotrx dotry FILL
  867.         end        
  868.     else if (num == 2) then do
  869.         circle x1 y1 dotrx dotry FILL
  870.         circle x3 y3 dotrx dotry FILL
  871.         end        
  872.     else if (num == 3) then do
  873.         circle x1 y1 dotrx dotry FILL
  874.         circle x2 y2 dotrx dotry FILL
  875.         circle x3 y3 dotrx dotry FILL
  876.         end        
  877.     else if (num == 4) then do
  878.         circle x1 y1 dotrx dotry FILL
  879.         circle x1 y3 dotrx dotry FILL
  880.         circle x3 y1 dotrx dotry FILL
  881.         circle x3 y3 dotrx dotry FILL
  882.         end        
  883.     else if (num == 5) then do
  884.         circle x1 y1 dotrx dotry FILL
  885.         circle x1 y3 dotrx dotry FILL
  886.         circle x3 y1 dotrx dotry FILL
  887.         circle x3 y3 dotrx dotry FILL
  888.         circle x2 y2 dotrx dotry FILL
  889.         end        
  890.     else if (num == 6) then do
  891.         circle x1 y1 dotrx dotry FILL
  892.         circle x1 y2 dotrx dotry FILL
  893.         circle x1 y3 dotrx dotry FILL
  894.         circle x3 y1 dotrx dotry FILL
  895.         circle x3 y2 dotrx dotry FILL
  896.         circle x3 y3 dotrx dotry FILL
  897.         end        
  898.     return 1
  899.  
  900.  
  901.  
  902. /* Returns the horizontal coordinate number of a spike, given the spike index number */
  903. getSpikeCenter: procedure 
  904.     parse arg spikenum
  905.  
  906.     if (spikenum == -1) then return 8
  907.     if (spikenum == 24) then return 8
  908.  
  909.     if (spikenum <= 5) then do
  910.         /* in the upper right quadrant */
  911.         return 15-spikenum
  912.     end
  913.     else if (spikenum <= 11) then do
  914.         /* in the upper left quadrant */
  915.         return (6-spikenum)+6
  916.     end
  917.     else if (spikenum <= 17) then do
  918.         /* in the lower left quadrant */
  919.         return (spikenum-17)+6
  920.     end
  921.     else do
  922.         /* in the lower right quadrant */
  923.         return (spikenum-18)+10
  924.     end
  925.  
  926. /* returns the base coordinate of a spike */
  927. getSpikeBase: procedure
  928.     parse arg spikenum
  929.  
  930.     if (spikenum > 11) then
  931.         return 16
  932.     else
  933.         return 0
  934.  
  935. /* returns the top coordinate of a spike */
  936. getSpikeTop: procedure
  937.     parse arg spikenum
  938.  
  939.     if (spikenum > 11) then
  940.         return 10
  941.     else
  942.         return 6
  943.  
  944. /* returns 1 if the spike hangs down, else -1 if it pokes up */
  945. getSpikeDir: procedure
  946.     parse arg spikenum
  947.  
  948.     if (spikenum > 11) then do
  949.         return -1
  950.         end
  951.     else do
  952.         return 1
  953.         end
  954.  
  955.  
  956. SetStatus: procedure
  957.     parse arg newstatus
  958.     
  959.     if (newstatus == "_LAST") then do
  960.         SetWindowTitle '"' || getclip("PrevString") || '"'
  961.         end
  962.     else do
  963.         call setclip("PrevString",newstatus)
  964.         SetWindowTitle '"' || newstatus || '"'
  965.     end
  966.     return 1
  967.     
  968.  
  969. /* draws an x over the dice for now */    
  970. ShowCantMove: procedure expose GlobData.
  971.     SetFPen 1
  972.     line GlobData.XSpace.7+1 GlobData.YSpace.6+1 GlobData.XSpace.9-1 GlobData.YSpace.10-1
  973.     line  GlobData.XSpace.9-1 GlobData.YSpace.6+1 GlobData.XSpace.7+1 GlobData.YSpace.10-1 
  974.     call SetStatus("Sorry, you can't move!  Click on the dice to continue")
  975.     return 1
  976.  
  977.  
  978.     
  979. /* returns 1 if a move is available, else returns 0 */
  980. CanMove: procedure expose GlobData.
  981.     do i = -1 to 24
  982.         if (PieceCanMove(i) == 1) then return 1
  983.         end                  
  984.     return 0
  985.     
  986.  
  987. /* returns 1 if the piece on slot i can move, else 0 */
  988. PieceCanMove: procedure expose GlobData.
  989.     parse arg from
  990.     
  991.     if ((GlobData.slots.from * GlobData.turn) <= 0) then return 0    
  992.     
  993.     do d = 0 to 3
  994.         if (GlobData.die.d > 0) then do
  995.             if (MoveOkay(from,from+(GlobData.die.d * GlobData.turn),1) ~= -1) then return 1
  996.             end  
  997.         end          
  998.     return 0
  999.     
  1000.     
  1001. /* returns 1 if a player can move a piece "out" to the given exit, else 0 */
  1002. CanMoveOut: procedure expose GlobData.
  1003.     parse arg playernum
  1004.  
  1005.     sum = GlobData.exited.playernum
  1006.     
  1007.     if (playernum == -1) then 
  1008.         do i = 0 to 5
  1009.             if (GlobData.slots.i < 0) then sum = sum - GlobData.slots.i
  1010.             end
  1011.     else if (playernum == 1) then
  1012.         do i = 18 to 23
  1013.             if (GlobData.slots.i > 0) then sum = sum + GlobData.slots.i
  1014.             end
  1015.             
  1016.     if (sum == 15) then return 1
  1017.     return 0
  1018.  
  1019.             
  1020. /* Draws arrows for possible moves for this piece */        
  1021. ShowMoves: procedure expose GlobData.
  1022.     parse arg from
  1023.     
  1024.     do d = 0 to 3
  1025.         if (GlobData.die.d > 0) then do
  1026.             if (MoveOkay(from,from+(GlobData.die.d * GlobData.Turn),1) ~= -1) then call DrawArrow(from+(GlobData.die.d * GlobData.Turn))
  1027.             end  
  1028.         end          
  1029.     return 1
  1030.  
  1031. /* Draw an arrow pointing to the specified spike */
  1032. DrawArrow: procedure expose GlobData.
  1033.     parse arg spikenum
  1034.     
  1035.     spikenum = ChopRange(spikenum,-1,24)
  1036.     
  1037.     hcoord = getSpikeCenter(spikenum)
  1038.     vcoord = getSpikeTop(spikenum) + getSpikeDir(spikenum)
  1039.  
  1040.     /* Special cases! */
  1041.     if (spikenum = -1) then    vcoord = 5
  1042.     if (spikenum = 24) then vcoord = 11
  1043.         
  1044.     SetFPen 1    
  1045.     circle GlobData.XSpace.hcoord GlobData.YSpace.vcoord trunc(GlobData.XSize/10) trunc(GlobData.YSize/10) FILL
  1046.     return 1
  1047.     
  1048.  
  1049. /* Clears all arrows from the board */    
  1050. ClearArrows: procedure expose GlobData.
  1051.     
  1052.     SetFPen 0
  1053.     
  1054.     /* right portion! */
  1055.     topofrect     = MidWayBetween(6,7,Y) + 1
  1056.     bottomofrect  = MidWayBetween(9,10,Y) - 1
  1057.     leftofrect    = MidWayBetween(9,10,X) + 1
  1058.     rightofrect   = MidWayBetween(15,16,X) - 1
  1059.     square leftofrect topofrect rightofrect bottomofrect FILL
  1060.     
  1061.     /* left portion! */
  1062.     leftofrect    = MidWayBetween(0,1,X)
  1063.     rightofrect  = MidWayBetween(6,7,X)
  1064.     square leftofrect topofrect rightofrect bottomofrect FILL 
  1065.     
  1066.     /* special cases! */
  1067.     circle GlobData.XSpace.8 GlobData.YSpace.5  trunc(GlobData.XSize/10) trunc(GlobData.YSize/10) FILL
  1068.     circle GlobData.XSpace.8 GlobData.YSpace.11 trunc(GlobData.XSize/10) trunc(GlobData.YSize/10) FILL 
  1069.     return 1    
  1070.     
  1071.  
  1072. /* Returns the point midway between two coords */
  1073. MidWayBetween: procedure expose GlobData.
  1074.     parse arg left, right, XorY
  1075.     if (XorY = X) then 
  1076.         return trunc((GlobData.XSpace.left + GlobData.XSpace.right)/2)
  1077.     else    
  1078.         return trunc((GlobData.YSpace.left + GlobData.YSpace.right)/2)
  1079.  
  1080. ChopRange: procedure
  1081.     parse arg myval, lo, hi
  1082.     if (myval < lo) then return lo
  1083.     if (myval > hi) then return hi
  1084.     return myval
  1085.     
  1086.     
  1087. /* --------------------------------------------------------------- */
  1088. /* procedure CheckForWin                                           */
  1089. /* --------------------------------------------------------------- */
  1090. CheckForWin: procedure expose GlobData.
  1091.     winner = nobody
  1092.     negone = -1
  1093.     
  1094.     if (GlobData.exited.1 == 15) then winner = "Player 1"
  1095.     else if (GlobData.exited.negone == 15) then winner = "Player 2"
  1096.     
  1097.     /* nobody one yet */
  1098.     if (winner == nobody) then return 1
  1099.     
  1100.     EasyRequest "Winner!" '"'||winner||" has won the game!"||'"' "Okay"
  1101.     call SetStatus("Game Over.  Rerun the script to play again.")
  1102.     lock off
  1103.     exit
  1104.     return 0
  1105.     
  1106.         
  1107. /* Transmit our move to our opponent */
  1108. TransmitMove: procedure 
  1109.     parse arg from, to
  1110.     
  1111.     sstring = '"' || from || " " || to || '"'
  1112.     sendmessage sstring
  1113.     return 1
  1114.     
  1115. /* This function determines whether or not a piece may exit using
  1116.    a given die number (that is larger than needed to get to the exit).  
  1117.    It will return 1 iff there is no way to use that same die number
  1118.    to move any other piece anywhere else. */
  1119. CanExitOnExtra: procedure expose GlobData.
  1120.     parse arg dienum
  1121.     
  1122.     /* Only check valid die */
  1123.     if (dienum <= 0) then return 0
  1124.         
  1125.     do i = 0 to 23
  1126.         if (((GlobData.slots.i)*(GlobData.turn)) > 0) then do
  1127.             if (MoveOkay(i, i+(dienum*GlobData.turn), 0) ~= -1) then return 0
  1128.             end
  1129.         end
  1130.     return 1    /* no moves in --> can move out */